/*
 * Copyright (C) 2010, Funcom AS, All Rights Reserved
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @authors
 *  Svein Gunnar Larsen <sveingunnarlarsen@gmail.com>
 *  Erik Thune Lund <culex.eriklund@gmail.com>
 *  Michel Krohn-Dale <kronicz@gmail.com>
 */

package com.funcom.peeler.peelable;

import com.funcom.peeler.BorderProperty;
import com.funcom.peeler.PeelStyleSheet;
import com.funcom.peeler.symbol.Symbol;
import com.jme.renderer.ColorRGBA;
import com.jmex.bui.Rule;
import com.jmex.bui.enumeratedConstants.HorizontalAlignment;
import com.jmex.bui.enumeratedConstants.TextEffect;
import com.jmex.bui.enumeratedConstants.VerticalAlignment;
import com.jmex.bui.property.BackgroundProperty;
import com.jmex.bui.property.CursorProperty;
import com.jmex.bui.property.FontProperty;
import com.jmex.bui.property.IconProperty;
import com.jmex.bui.util.Dimension;
import com.jmex.bui.util.Insets;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Used to serialize styleclass rules into strings for .bss files
 */
public class StyleSheetExporter {

    /**
     * Serializes a styleclass in this stylesheet into a string
     * @param stylesheet the PeelStyleSheet to obtain rules from
     * @param fqStyle the styleclass to serialize
     * @return the serialized styleclass
     */
    public static String exportStyle( PeelStyleSheet stylesheet, String fqStyle ) {

        if ( stylesheet.properties.get( fqStyle ) == null ) return null;

        return exportRule( stylesheet.properties.get( fqStyle ) );

    }

    /**
     * Serializes an entire stylesheet into a string
     * @param stylesheet the PeelStyleSheet to serialize
     * @return the serialized PeelStyleSheet
     */
    public static String exportStyleSheet( PeelStyleSheet stylesheet ) {

        if ( stylesheet == null ) return null;

        String out = new String();

        for ( Rule rule : stylesheet.properties.values() ) {

            String ruleString = exportRule( rule );
            if ( rule != null ) out += ruleString;
        }

        return out;
    }

    /**
     * Serializes a set of styleclasses into a string
     * @param stylesheet the PeelStyleSheet to obtain rules from
     * @param fqStyles the Set of styleclasses to serializes
     * @return the serialized styleclasses
     */
    public static String exportStyles( PeelStyleSheet stylesheet, Set< String > fqStyles ) {

        String out = new String();

        for ( String style : fqStyles ) {
            String styleString = exportStyle( stylesheet, style );
            if ( styleString == null ) continue;
            out += styleString;
        }

        return out;
    }

    /**
     * Serializes a Rule into a String
     * @param rule the Rule to serialize
     * @return the serialized Rule
     */
    public static String exportRule( Rule rule ) {

        if ( rule == null ) return null;
        if ( rule.styleClass == null ) return null;

        // Classname, pseudoclass and opening brace
        String out = new String( rule.styleClass );
        if ( rule.pseudoClass != null ) out += ":" + rule.pseudoClass;
        out += " {\n";

        // Serialize all properties in the rule
        for ( Map.Entry< String, Object > value : rule.properties.entrySet() ) {

            String property_line = propertyToString( value.getKey(), value.getValue() );

            // Pretty the rule up with indentation and newlines
            if ( property_line != null ) {
                out += ( '\t' + property_line + '\n' );
            }
        }

        out += "}\n\n"; // Closing brace plus empty line beneath styleclass

        return out;
    }

    /**
     * Serialize a Collection of Rules into a String
     * @param rules the Collection of Rules to serialize
     * @return the serialized Rules
     */
    public static String exportRules( Collection< Rule > rules ) {

        if ( rules == null ) return null;

        String out = new String();

        for ( Rule rule : rules ) {

            String ruleString = exportRule( rule );
            if ( ruleString != null ) out += ruleString;
        }

        return out;
    }

    /**
     * Retrieve the relevant rules for a given component, climbs the heirarchy of rules
     * if necessary. Retrieves the rules for any children of the component at the same time.
     * @param component the PeelComponent to retrieve the Rules for
     * @return the set of Rules relevant to this PeelComponent and all it's children
     */
    public static Set< Rule > getRelevantRules( PeelComponent component ) {

        Set< Rule > rules = new HashSet< Rule >();

        if ( component instanceof PeelContainer ) {

            for ( PeelComponent child : ((PeelContainer)component).getChildren() ) {
                if ( child != null ) {
                    rules.addAll( getRelevantRules( child ) );
                }
            }
        }

        for ( Rule rule : component.getStyleSheet().properties.values() ) {

            if ( rule.styleClass.equals( component.getComponent().getStyleClass() ) ) {
                rules.add( rule );
                rules.addAll( getParentRules( rule, component.getStyleSheet().properties ) );
            }
        }

        return rules;
    }

    /**
     * Retrieves a parent Rule for this Rule and their parent Rule in turn
     * @param rule the Rule to check for a parent Rule
     * @param rules the Map of Rules to retrieve parent rules from
     * @return the Collection of parent Rules that have been found
     */
    public static Collection< Rule > getParentRules( Rule rule, Map< String, Rule > rules ) {

        Collection< Rule > parent_rules = new HashSet< Rule >();

        if ( rule.properties.containsKey( Symbol.StyleProperty.parent ) ) {
            parent_rules.add( (Rule)rule.properties.get( Symbol.StyleProperty.parent ) );
            parent_rules.addAll( getParentRules( (Rule)rule.properties.get( Symbol.StyleProperty.parent ), rules ) );
        }

        return parent_rules;
    }

    /**
     * Serializes a given Property into a String through use of more specific serializers
     * after casting to the appropriate Property type
     * @param name the type of Property to serialize
     * @param value the Property to serialize
     * @return the serialized Property or the result of toString() if it's an unknown Property
     */
    public static String propertyToString( String name, Object value ) {

        if ( value == null ) return null;

        if ( name.equals( Symbol.StyleProperty.background ) ) return backgroundString( (BackgroundProperty)value );
        if ( name.equals( Symbol.StyleProperty.cursor ) ) return cursorString( (CursorProperty)value );
        if ( name.equals( Symbol.StyleProperty.font ) ) return fontString( (FontProperty)value );
        if ( name.equals( Symbol.StyleProperty.color ) ) return colorString( (ColorRGBA)value );
        if ( name.equals( Symbol.StyleProperty.icon ) ) return iconString( (IconProperty)value );
        if ( name.equals( Symbol.StyleProperty.effect_color ) ) return effectColorString( (ColorRGBA)value );
        if ( name.equals( Symbol.StyleProperty.effect_size ) ) return effectSizeString( (Integer)value );
        if ( name.equals( Symbol.StyleProperty.text_align ) ) return textAlignString( (HorizontalAlignment)value );
        if ( name.equals( Symbol.StyleProperty.text_effect ) ) return textEffectString( (TextEffect)value );
        if ( name.equals( Symbol.StyleProperty.vertical_align ) ) return verticalAlignString( (VerticalAlignment)value );
        if ( name.equals( Symbol.StyleProperty.line_spacing ) ) return lineSpacingString( (Integer)value );
        if ( name.equals( Symbol.StyleProperty.padding ) ) return paddingString( (Insets)value );
        if ( name.equals( Symbol.StyleProperty.parent ) ) return parentString( (Rule)value );
        if ( name.equals( Symbol.StyleProperty.tooltip ) ) return toolTipString( (String)value );
        if ( name.equals( Symbol.StyleProperty.size ) ) return sizeString( (Dimension)value );
        if ( name.contains( Symbol.StyleProperty.border ) ) return borderString( (BorderProperty)value );

        // Custom values should have toString() implemented for best effect
        return value.toString();
    }

    /**
     * Serializes a BackgroundProperty into a style rule String.
     * Handles solid backgrounds( TintedBackground ),
     * image backgrounds( ImageBackground ) and blank
     * backgrounds.
     *
     * For image backgrounds it will serialize the layout
     * of the background, the path and padding that may
     * be present
     *
     * @param prop The background property which will be serialized
     * @return the serialized style rule
     */
    protected static String backgroundString( BackgroundProperty prop ) {

        String out = Symbol.StyleProperty.background + ": ";

        if ( prop.type.equals( "" ) ) return null;

        if ( prop.type.equals( "solid" ) ) {
            out += prop.type;

        out += " " + colorRGBAToString( prop.color );

        } else if ( prop.type.equals( "image" ) ) {

            out += prop.type;
            out += " \"" + prop.ipath + "\" ";

            out += prop.scaleMode.stylesheetAttribute + " ";

            if ( prop.frame != null ) {
                out += prop.frame.top + " ";
                out += prop.frame.right + " ";
                out += prop.frame.bottom + " ";
                out += prop.frame.left;
            }
        }
        else if ( prop.type.equals( "blank" ) ) {
            out += prop.type;
        }

        out += ";";

        return out;
    }

    /**
     * Serializes the path to this cursor into a style rule String.
     * @param prop The CursorProperty to be serialized
     * @return the serialized style rule
     */
    protected static String cursorString( CursorProperty prop ) {

        String out = Symbol.StyleProperty.cursor + ": ";
        out += prop.name + ";";
        return out;
    }

    /**
     * Serializes the FontProperty it is given into a font family,
     * font style and font size into a style rule String
     * @param prop is the FontProperty to serialize
     * @return the serialized FontProperty
     */
    protected static String fontString( FontProperty prop ) {

        String out = Symbol.StyleProperty.font + ": ";
        if ( !prop.family.equals( "" ) ) {
            out += "\"" + prop.family + "\"" + " ";
        }
        else {
            out += "\"" + "Tahoma" + "\"" + " ";
        }
        out += prop.style + " ";
        if ( prop.size != 0 ) out += prop.size+ ";";
        return out;
    }

    /**
     * Serializes an IconProperty into a style rule String
     * handles blank icons, image icons and their dimensions
     * @param prop is the IconProperty to be serialized
     * @return the serialized IconProperty
     */
    protected static String iconString( IconProperty prop ) {

        String out = Symbol.StyleProperty.icon + ": ";

        if ( prop.type.equals( "blank" ) ) {
            out += prop.type + " " + prop.width + " " + prop.height;
        }
        else if ( prop.type.equals( "image" ) ) {
            out += prop.type + " " + "\"" + prop.ipath + "\"";
        }

        out+= ";";
        return out;
    }

    /**
     * Serializes a ColorRGBA into a style rule String for text color
     * @param prop is the ColorRGBA to be serialized
     * @return the rule string for text color
     */
    protected static String colorString( ColorRGBA prop ) {

        String out = Symbol.StyleProperty.color + ": ";
        out += " " + colorRGBAToString( prop );
        out += ";";
        return out;
    }

    /**
     * Serializes a ColorRGBA into a style rule String for text effect color
     * @param prop the ColorRGBA to be serialized
     * @return the rule string for text effect color
     */
    protected static String effectColorString( ColorRGBA prop ) {

        String out = Symbol.StyleProperty.effect_color + ": ";
        out += " " + colorRGBAToString( prop );
        out += ";";
        return out;
    }

    /**
     * Serializes an int into a rule string for text effect size
     * @param prop the int be serialized as the text effect size
     * @return the rule string for text effect size
     */
    protected static String effectSizeString( int prop ) {

        String out = Symbol.StyleProperty.effect_size + ": ";
        out += " " + prop;
        out += ";";
        return out;
    }

    /**
     * Serializes an int into a rule string for line spacing style property
     * @param prop the int to be serialized as the line spacing property
     * @return the rule string for the line spacing
     */
    protected static String lineSpacingString( int prop ) {

        String out = Symbol.StyleProperty.line_spacing + ": ";
        out += " " + prop;
        out += ";";
        return out;
    }

    /**
     * Serializes an Insets into a rule string for padding style property
     * @param prop the Insets to be serialized
     * @return the rule string for the padding style
     */
    protected static String paddingString( Insets prop ) {

        String out = Symbol.StyleProperty.padding + ": ";
        out += " " + prop.top;
        out += " " + prop.right;
        out += " " + prop.bottom;
        out += " " + prop.left;
        out += ";";
        return out;
    }

    /**
     * Serializes a Rule into a rule string for parent style property
     * @param prop the Rule to use as a parent style
     * @return the rule string for the parent style property
     */
    protected static String parentString( Rule prop ) {

        String out = Symbol.StyleProperty.parent + ": ";
        out += " " + prop.styleClass + ";";
        return out;
    }

    /**
     * Serializes a Dimension into a style rule string for size style property
     * @param prop the Dimension to be serialized
     * @return the rule string for the size style property
     */
    protected static String sizeString( Dimension prop ) {

        String out = Symbol.StyleProperty.size + ": ";
        out += prop.getWidth();
        out += " ";
        out += prop.getHeight();
        out += ";";
        return out;
    }

    /**
     * Serializes a HorizontalAlignment property into a style rule string for horizontal
     * alignment style
     * @param prop the HorizontalAlignment to be serializded
     * @return the rule string for the horizontal alignment style property
     */
    protected static String textAlignString( HorizontalAlignment prop ) {

        String out = Symbol.StyleProperty.text_align + ": ";
        out += " " + prop.stylesheetAttribute;
        out += ";";
        return out;
    }

    /**
     * Serializes a TextEffect property into a rule style string for
     * the type of text effect style
     * @param prop the TextEffect to be serialized
     * @return the rule style string for the text effect style property
     */
    protected static String textEffectString( TextEffect prop ) {

        String out = Symbol.StyleProperty.text_effect + ": ";
        out += " " + prop.stylesheetAttribute;
        out += ";";
        return out;
    }

    /**
     * Serializes a String into a rule style string for tooltip style
     * @param prop the String to serializes into a styleclass for tooltip style
     * @return the rule style string for the tooltip style property
     */
    protected static String toolTipString( String prop ) {

        String out = Symbol.StyleProperty.tooltip + ": ";
        out += " " + prop + ";";
        return out;
    }

    /**
     * Serializes a VerticalAlignment property into a style rule string for vertical
     * alignment style
     * @param prop the HorizontalAlignment to be serializded
     * @return the rule string for the horizontal alignment style property
     */
    protected static String verticalAlignString( VerticalAlignment prop ) {

        String out = Symbol.StyleProperty.vertical_align + ": ";
        out += " " + prop.stylesheetAttribute;
        out += ";";
        return out;
    }

    /**
     * Serializes a BorderProperty property into a rule style string for
     * border style
     * @param prop the BorderProperty to be serialized
     * @return the rule style string for the border style property
     */
    protected static String borderString( BorderProperty prop ) {

        String out = prop.sides + ": ";
        out += prop.thickness + " ";
        out += prop.type + " ";
        if ( prop.color != null ) out += colorRGBAToString( prop.color );
        out += ";";
        return out;
    }

    /**
     * serializes a ColorRGBA into a string useful for a .bss style rule
     * @param prop the ColorRGBA to serialize
     * @return the serialized ColorRGBA string
     */
    protected static String colorRGBAToString( ColorRGBA prop ) {

        String out = new String();

        // Put the color in the right format
        String red = Integer.toHexString( (int)(prop.r * 255 ) );
        String green = Integer.toHexString( (int)(prop.g * 255 ) );
        String blue = Integer.toHexString( (int)(prop.b * 255 ) );
        String alpha = Integer.toHexString( (int)(prop.a * 255 ) );

        red = ( red.length() < 2 ) ? ( 0 + red ) : red;
        green = ( green.length() < 2 ) ? ( 0 + green ) : green;
        blue = ( blue.length() < 2 ) ? ( 0 + blue ) : blue;
        alpha = ( alpha.length() < 2 ) ? ( 0 + alpha ) : alpha;

        out+= "#" + red + green + blue + alpha;

        return out;
    }
}
